#include "param.h"
#include "types.h"
#include "defs.h"
#include "x86.h"
#include "memlayout.h"
#include "mmu.h"
#include "proc.h"
#include "elf.h"
#include "fs.h"
#include "file.h"
#include "fcntl.h"

extern char data[];  // defined by kernel.ld
pde_t *kpgdir;  // for use in scheduler()
struct segdesc gdt[NSEGS];


int order = 1;
uint findNextPageToSwap(void);
int loadFromPageFile( void * va);
int storeInPageFile( void * va);

// Set up CPU's kernel segment descriptors.
// Run once on entry on each CPU.
void
seginit(void)
{
  struct cpu *c;

  // Map "logical" addresses to virtual addresses using identity map.
  // Cannot share a CODE descriptor for both kernel and user
  // because it would have to have DPL_USR, but the CPU forbids
  // an interrupt from CPL=0 to DPL=3.
  c = &cpus[cpunum()];
  c->gdt[SEG_KCODE] = SEG(STA_X|STA_R, 0, 0xffffffff, 0);
  c->gdt[SEG_KDATA] = SEG(STA_W, 0, 0xffffffff, 0);
  c->gdt[SEG_UCODE] = SEG(STA_X|STA_R, 0, 0xffffffff, DPL_USER);
  c->gdt[SEG_UDATA] = SEG(STA_W, 0, 0xffffffff, DPL_USER);

  // Map cpu, and curproc
  c->gdt[SEG_KCPU] = SEG(STA_W, &c->cpu, 8, 0);

  lgdt(c->gdt, sizeof(c->gdt));
  loadgs(SEG_KCPU << 3);
  
  // Initialize cpu-local storage.
  cpu = c;
  proc = 0;
}

// Return the address of the PTE in page table pgdir
// that corresponds to virtual address va.  If alloc!=0,
// create any required page table pages.
static pte_t *
walkpgdir(pde_t *pgdir, const void *va, int alloc)
{
  pde_t *pde;
  pte_t *pgtab;

  pde = &pgdir[PDX(va)];
  if(*pde & PTE_P){
    pgtab = (pte_t*)p2v(PTE_ADDR(*pde));
  } else {
    if(!alloc || (pgtab = (pte_t*)kalloc()) == 0)
      return 0;	
    // Make sure all those PTE_P bits are zero.
    memset(pgtab, 0, PGSIZE);
    // The permissions here are overly generous, but they can
    // be further restricted by the permissions in the page table 
    // entries, if necessary.
    *pde = v2p(pgtab) | PTE_P | PTE_W | PTE_U;
  }
  //cprintf("test: %x\n",&pgtab[PTX(va)]);
  return &pgtab[PTX(va)];
}

// Create PTEs for virtual addresses starting at va that refer to
// physical addresses starting at pa. va and size might not
// be page-aligned.
static int
mappages(pde_t *pgdir, void *va, uint size, uint pa, int perm)
{
  char *a, *last;
  pte_t *pte;
  
  a = (char*)PGROUNDDOWN((uint)va);
  last = (char*)PGROUNDDOWN(((uint)va) + size - 1);
  for(;;){  
    if((pte = walkpgdir(pgdir, a, 1)) == 0)
      return -1;
    if(*pte & PTE_P)
      panic("remap");
    *pte = pa | perm | PTE_P;
    if(a == last)
      break;
    a += PGSIZE;
    pa += PGSIZE;
  }
  return 0;
}

// There is one page table per process, plus one that's used when
// a CPU is not running any process (kpgdir). The kernel uses the
// current process's page table during system calls and interrupts;
// page protection bits prevent user code from using the kernel's
// mappings.
// 
// setupkvm() and exec() set up every page table like this:
//
//   0..KERNBASE: user memory (text+data+stack+heap), mapped to
//                phys memory allocated by the kernel
//   KERNBASE..KERNBASE+EXTMEM: mapped to 0..EXTMEM (for I/O space)
//   KERNBASE+EXTMEM..data: mapped to EXTMEM..V2P(data)
//                for the kernel's instructions and r/o data
//   data..KERNBASE+PHYSTOP: mapped to V2P(data)..PHYSTOP, 
//                                  rw data + free physical memory
//   0xfe000000..0: mapped direct (devices such as ioapic)
//
// The kernel allocates physical memory for its heap and for user memory
// between V2P(end) and the end of physical memory (PHYSTOP)
// (directly addressable from end..P2V(PHYSTOP)).

// This table defines the kernel's mappings, which are present in
// every process's page table.
static struct kmap {
  void *virt;
  uint phys_start;
  uint phys_end;
  int perm;
} kmap[] = {
  { (void*) KERNBASE, 0,             EXTMEM,    PTE_W},  // I/O space
  { (void*) KERNLINK, V2P(KERNLINK), V2P(data), 0}, // kernel text+rodata
  { (void*) data,     V2P(data),     PHYSTOP,   PTE_W},  // kernel data, memory
  { (void*) DEVSPACE, DEVSPACE,      0,         PTE_W},  // more devices
};

// Set up kernel part of a page table.
pde_t*
setupkvm()
{
  pde_t *pgdir;
  struct kmap *k;
  if((pgdir = (pde_t*)kalloc()) == 0)
    return 0;
  memset(pgdir, 0, PGSIZE);
  if (p2v(PHYSTOP) > (void*)DEVSPACE)
    panic("PHYSTOP too high");
  for(k = kmap; k < &kmap[NELEM(kmap)]; k++)
    if(mappages(pgdir, k->virt, k->phys_end - k->phys_start, 
                (uint)k->phys_start, k->perm) < 0)
      return 0;
  return pgdir;
}

// Allocate one page table for the machine for the kernel address
// space for scheduler processes.
void
kvmalloc(void)
{
  kpgdir = setupkvm();
  switchkvm();
}

// Switch h/w page table register to the kernel-only page table,
// for when no process is running.
void
switchkvm(void)
{
  lcr3(v2p(kpgdir));   // switch to the kernel page table
}

// Switch TSS and h/w page table to correspond to process p.
void
switchuvm(struct proc *p)
{
  pushcli();
  cpu->gdt[SEG_TSS] = SEG16(STS_T32A, &cpu->ts, sizeof(cpu->ts)-1, 0);
  cpu->gdt[SEG_TSS].s = 0;
  cpu->ts.ss0 = SEG_KDATA << 3;
  cpu->ts.esp0 = (uint)proc->kstack + KSTACKSIZE;
  ltr(SEG_TSS << 3);
  if(p->pgdir == 0)
    panic("switchuvm: no pgdir");
  lcr3(v2p(p->pgdir));  // switch to new address space
  popcli();
}

// Load the initcode into address 0 of pgdir.
// sz must be less than a page.
void
inituvm(pde_t *pgdir, char *init, uint sz)
{
  char *mem;
  
  if(sz >= PGSIZE)
    panic("inituvm: more than a page");
  mem = kalloc();
  memset(mem, 0, PGSIZE);
  mappages(pgdir, 0, PGSIZE, v2p(mem), PTE_W|PTE_U);
  memmove(mem, init, sz);
}

// Load a program segment into pgdir.  addr must be page-aligned
// and the pages from addr to addr+sz must already be mapped.
int
loaduvm(pde_t *pgdir, char *addr, struct inode *ip, uint offset, uint sz)
{
  uint i, pa, n;
  pte_t *pte;

  if((uint) addr % PGSIZE != 0)
    panic("loaduvm: addr must be page aligned");
  for(i = 0; i < sz; i += PGSIZE){
    if((pte = walkpgdir(pgdir, addr+i, 0)) == 0)
      panic("loaduvm: address should exist");
    pa = PTE_ADDR(*pte);
    if(sz - i < PGSIZE)
      n = sz - i;
    else
      n = PGSIZE;
    if(readi(ip, p2v(pa), offset+i, n) != n)
      return -1;
  }
  return 0;
}

int
calculateNumberOfPages(){
	int i,counter = 0;
	for(i = 0; i < TOTAL_PSYC_PAGES; i++){
		if (proc->mt[i].pa != 0)
			counter++;
			//cprintf("test: %x\n",proc->mt[i].pa);
	}
	return counter;
}

int
calculateNumberOfPsycPages(){
	int i,counter = 0;
	for(i = 0; i < TOTAL_PSYC_PAGES; i++){
		if (proc->mt[i].inMemory)
			counter++;
	}
	return counter;
}
// Allocate page tables and physical memory to grow process from oldsz to
// newsz, which need not be page aligned.  Returns new size or 0 on error.
int
allocuvm(pde_t *pgdir, uint oldsz, uint newsz)
{
	char *mem;
	uint a;
	
	#ifndef NONE
		int currentNumberOfPages = calculateNumberOfPages();
		int currentNumberOfPsycPages = calculateNumberOfPsycPages();
	#endif
	
	if(newsz >= KERNBASE)
		return 0;
	if(newsz < oldsz) {
		cprintf("warning: returning oldsize"); //TODO: delete me
		return oldsz;
	}

	a = PGROUNDUP(oldsz);
		
		#ifndef NONE
		int i;
		for(i = 0; i < 30; i++){
				pte_t * pte;
				pte = walkpgdir(proc->pgdir,(void*)(i << PGSHIFT),0);
			cprintf("pte %d | value %x\n",i ,PTE_ADDR(*pte));
		}
		#endif
	
	
	int counter = 0;
	for(; a < newsz; a += PGSIZE){
		#ifndef NONE
		cprintf("\na is %d | newsz is %d\n",a,newsz);
		//cprintf("currentNumberofPages is: %d\n",currentNumberOfPages + counter);
		cprintf("currentNumberOfPsycPages is: %d\n",currentNumberOfPsycPages+ counter);
		if (currentNumberOfPages + counter <= 30){
			if (currentNumberOfPsycPages + counter == 15){
				uint nextPage = findNextPageToSwap();
				cprintf("next page to swap, addr: %x\n",nextPage);
				if (storeInPageFile((void *)nextPage) < 0){
					return 0;
				}
				currentNumberOfPsycPages--;
				//currentNumberOfPages--;
			}
		}else{
			return 0;
		}
		#endif
		mem = kalloc();
		if (mem == 0) {
			cprintf("allocuvm out of memory\n");
			deallocuvm(pgdir, newsz, oldsz);
			return 0;
		}

		counter++;

		//cprintf("currentNumberOfPsycPages is: %d\n",currentNumberOfPsycPages+ counter);
		
		#ifndef NONE
		
		if (proc->pid > 2){
			int i = PTX(a);
			
			//for(i = 0; i < TOTAL_PSYC_PAGES; i++){
				//if (proc->mt[i].pa == 0){
				cprintf("index is %d\n",i);
				proc->mt[i].pa = (uint) v2p(mem);
				proc->mt[i].order = order;
				proc->mt[i].refString = 0;
				order++;
				proc->mt[i].offset = -1;
				proc->mt[i].inMemory = 1;
				cprintf("create new page with virtual: %x | psyc: %x | order %d \n",mem,(uint)v2p(mem),proc->mt[i].order);
				cprintf("currentNumberofPages is: %d\n",currentNumberOfPages + counter);
					//break;
				//}
			//}
		}
		#endif
		memset(mem, 0, PGSIZE);
		//cprintf("#######enter mappages#########\n");
		mappages(pgdir, (char*)a, PGSIZE,(uint) v2p(mem), PTE_W | PTE_U);
	}
	cprintf("exit allocuvn, returning newsz %d\n", newsz);
	return newsz;
}

// Deallocate user pages to bring the process size from oldsz to
// newsz.  oldsz and newsz need not be page-aligned, nor does newsz
// need to be less than oldsz.  oldsz can be larger than the actual
// process size.  Returns the new process size.
int
deallocuvm(pde_t *pgdir, uint oldsz, uint newsz)
{
  pte_t *pte;
  uint a, pa;
cprintf("enter deallocuvm...\n");
  if(newsz >= oldsz)
    return oldsz;

  a = PGROUNDUP(newsz);
  for(; a  < oldsz; a += PGSIZE){
	  
    pte = walkpgdir(pgdir, (char*)a, 0);
    if(!pte)
      a += (NPTENTRIES - 1) * PGSIZE;
    else if((*pte & PTE_P) != 0){
      pa = PTE_ADDR(*pte);
      if(pa == 0)
        panic("kfree");
      char *v = p2v(pa);
      kfree(v);
	
	#ifndef NONE
	// cprintf("deallocuvm: a is: %d\n",a);
	//  cprintf("deallocuvm: pgdir is: %d | proc->pgdir is: %d\n",pgdir,proc->pgdir);
	  if (proc->pgdir == pgdir){ //&&  (*pte & PTE_U)) { 
		  cprintf("-----------enter dea\n");
			int i = PTX(a);
			proc->mt[i].pa = 0;
			proc->mt[i].order = -1;
			proc->mt[i].offset = -1;
			proc->mt[i].inMemory = 0;
		}  
	#endif
// if (*pte & PTE_U) { 
// 	cprintf("-----------enter dea\n");
// 	int i = PTX(a);
// 	proc->mt[i].pa = 0;
// 	proc->mt[i].order = -1;
// 	proc->mt[i].offset = -1;
// 	proc->mt[i].inMemory = 0;
// } 
      *pte = 0; 
    }
  }
  cprintf("exit deallocuvm\n");
  return newsz;
}

// Free a page table and all the physical memory pages
// in the user part.
void
freevm(pde_t *pgdir)
{
  uint i;
  if(pgdir == 0)
    panic("freevm: no pgdir");
  deallocuvm(pgdir, KERNBASE, 0);
  for(i = 0; i < NPDENTRIES; i++){
    if(pgdir[i] & PTE_P){
      char * v = p2v(PTE_ADDR(pgdir[i]));
      kfree(v);
    }
  }
  kfree((char*)pgdir);
}

// Clear PTE_U on a page. Used to create an inaccessible
// page beneath the user stack.
void
clearpteu(pde_t *pgdir, char *uva)
{
  pte_t *pte;

  pte = walkpgdir(pgdir, uva, 0);
  if(pte == 0)
    panic("clearpteu");
  *pte &= ~PTE_U;
}

pde_t*
copyuvm(pde_t *pgdir, uint sz, struct proc* np)
{
	pde_t *d;
	pte_t *pte;
	uint pa, i;
	char *mem;
	cprintf("enter copyuvm\n\n");
	// 	int j;
	// 	for(j = 0; j < 30; j++){
		// 			pte_t * pte;
	// 		pte = walkpgdir(proc->pgdir,(void*)(j << PGSHIFT),0);
	// 		cprintf("pte %d | value %x | present %d | pageOut %d\n",j ,PTE_ADDR(*pte),*pte & PTE_P,*pte & PTE_PG);
	// 	}
	//int pageOut = 0;
// 	#ifndef NONE
// 	int pageArray[TOTAL_PSYC_PAGES];
// 	for(i = 0; i < TOTAL_PSYC_PAGES; i++)
// 		pageArray[i] = 0;
// 	
// 	for(i = 0; i < sz; i+=PGSIZE){
// 		if((pte = walkpgdir(pgdir, (void *) i, 0)) == 0)
// 			panic("copyuvm: pte should exist");
// 		if (!(*pte & PTE_P)){
// 			pageArray[PTX(i)] = 1;
// 		}
// 	}
// 	#endif
	if((d = setupkvm()) == 0)
		return 0;
	for(i = 0; i < sz; i += PGSIZE){
		
		if((pte = walkpgdir(pgdir, (void *) i, 0)) == 0)
			panic("copyuvm: pte should exist");
		if(!(*pte & PTE_P)){
			#ifndef NONE
			if (!(*pte & PTE_PG)){
				panic("copyuvm: page not present"); 
			} else{
// 				uint nextPage = findNextPageToSwap();
// 				cprintf("\ncopyuvm: next page is %x | va is %x | pageout: %d\n",nextPage,i,*pte & PTE_PG);
// 				storeInPageFile((void *)nextPage);
// 				loadFromPageFile((void*) i);
// 				if((pte = walkpgdir(pgdir, (void *) i, 0)) == 0)
// 					panic("copyuvm: pte should exist");
// 				cprintf("\ncopyuvm: next page is %x | va is %x | pageout: %d\n",nextPage,i,*pte & PTE_PG);
				int mtIndex = PTX(i);
				if (np->parent->pid <= 2){
					np->mt[mtIndex].inMemory = 0;
					np->mt[mtIndex].refString = 0;
					np->mt[mtIndex].order = 0;
					np->mt[mtIndex].offset = -1;
					np->mt[mtIndex].pa = 0;
				} else {
					np->mt[mtIndex].refString = proc->mt[mtIndex].refString;
					np->mt[mtIndex].order = proc->mt[mtIndex].order;
					np->mt[mtIndex].offset = proc->mt[mtIndex].offset;
					np->mt[mtIndex].inMemory = proc->mt[mtIndex].inMemory;
					np->mt[mtIndex].pa = 0;
				}
				continue;
			}
			#else
				panic("copyuvm: page not present"); 
			#endif
		}
		
		pa = PTE_ADDR(*pte);
		if((mem = kalloc()) == 0)
			goto bad;
		
		memmove(mem, (char*)p2v(pa), PGSIZE);
		if(mappages(d, (void*)i, PGSIZE, v2p(mem), PTE_W | PTE_U) < 0)
			goto bad;
		
		#ifndef NONE
// 			pte_t * nPte;
// 			if((nPte = walkpgdir(d, (void *) i, 0)) == 0)
// 				panic("copyuvm: nPte should exist");
// 			if (pageArray[PTX(i)] == 1){
// 				*nPte = ((*nPte | PTE_PG) & ~PTE_P);
// 			}
// 			
// 			
// 			if (!(*pte & PTE_PG) ) { 
// 				np->mt[mtIndex].pa = (uint) v2p(mem);
// 			} else{
// 				np->mt[mtIndex].pa = (uint) v2p(mem);
// 				kfree(mem);
// 			}
			int mtIndex = PTX(i);
			np->mt[mtIndex].pa = (uint) v2p(mem);
			if (np->parent->pid <= 2){
				np->mt[mtIndex].inMemory = 1;
				np->mt[mtIndex].refString = 0;
				np->mt[mtIndex].order = order;
				order++;
				np->mt[mtIndex].offset = -1;
			}
			else {
				np->mt[mtIndex].refString = proc->mt[mtIndex].refString;
				np->mt[mtIndex].order = proc->mt[mtIndex].order;
				np->mt[mtIndex].offset = proc->mt[mtIndex].offset;
				np->mt[mtIndex].inMemory = proc->mt[mtIndex].inMemory;
			}
			#endif
	}
	
	#ifndef NONE //TODO remove this print
	for(i = 0; i < 30; i++){
		pte_t * pte;
		pte = walkpgdir(proc->pgdir,(void*)(i << PGSHIFT),0);
		cprintf("pte %d | value %x | present %d | pageOut %d\n",i ,PTE_ADDR(*pte),*pte & PTE_P,*pte & PTE_PG);
	}
	cprintf("for new pgdir\n");
	for(i = 0; i < 30; i++){
		pte_t * pte;
		pte = walkpgdir(d,(void*)(i << PGSHIFT),0);
		cprintf("pte %d | value %x | present %d | pageOut %d\n",i ,PTE_ADDR(*pte),*pte & PTE_P,*pte & PTE_PG);
	}
	#endif
	cprintf("\n\nexit copyuvm\n");
	return d;
	
	bad:
	freevm(d);
	return 0;
}
// Given a parent process's page table, create a copy
// of it for a child.
// pde_t*
// copyuvm(pde_t *pgdir, uint sz, struct proc* np)
// {
// 	pde_t *d;
// 	pte_t *pte;
// 	uint pa, i;
// 	char *mem;
// 	cprintf("enter copyuvm\n\n");
// // 	int j;
// // 	for(j = 0; j < 30; j++){
// // 			pte_t * pte;
// // 		pte = walkpgdir(proc->pgdir,(void*)(j << PGSHIFT),0);
// // 		cprintf("pte %d | value %x | present %d | pageOut %d\n",j ,PTE_ADDR(*pte),*pte & PTE_P,*pte & PTE_PG);
// // 	}
// 	//int pageOut = 0;
// 	#ifndef NONE
// 		int pageArray[TOTAL_PSYC_PAGES];
// 		for(i = 0; i < TOTAL_PSYC_PAGES; i++)
// 			pageArray[i] = 0;
// 		
// 		for(i = 0; i < sz; i+=PGSIZE){
// 			if((pte = walkpgdir(pgdir, (void *) i, 0)) == 0)
// 				panic("copyuvm: pte should exist");
// 			if (!(*pte & PTE_P)){
// 				pageArray[PTX(i)] = 1;
// 			}
// 		}
// 	#endif
// 	if((d = setupkvm()) == 0)
// 		return 0;
// 	for(i = 0; i < sz; i += PGSIZE){
// 		if((pte = walkpgdir(pgdir, (void *) i, 0)) == 0)
// 			panic("copyuvm: pte should exist");
// 		if(!(*pte & PTE_P)){
// 			#ifndef NONE
// 			if (!(*pte & PTE_PG)){
// 				panic("copyuvm: page not present"); 
// 			} else{
// 				uint nextPage = findNextPageToSwap();
// 				cprintf("\ncopyuvm: next page is %x | va is %x | pageout: %d\n",nextPage,i,*pte & PTE_PG);
// 				storeInPageFile((void *)nextPage);
// 				loadFromPageFile((void*) i);
// 				if((pte = walkpgdir(pgdir, (void *) i, 0)) == 0)
// 					panic("copyuvm: pte should exist");
// 				cprintf("\ncopyuvm: next page is %x | va is %x | pageout: %d\n",nextPage,i,*pte & PTE_PG);
// 			}
// 			#else
// 				panic("copyuvm: page not present"); 
// 			#endif
// 		}
// 		
// 		pa = PTE_ADDR(*pte);
// 		if((mem = kalloc()) == 0)
// 			goto bad;
// 		
// 		memmove(mem, (char*)p2v(pa), PGSIZE);
// 		if(mappages(d, (void*)i, PGSIZE, v2p(mem), PTE_W | PTE_U) < 0)
// 			goto bad;
// 		
// 		#ifndef NONE
// 			pte_t * nPte;
// 			if((nPte = walkpgdir(d, (void *) i, 0)) == 0)
// 				panic("copyuvm: nPte should exist");
// 			if (pageArray[PTX(i)] == 1){
// 				*nPte = ((*nPte | PTE_PG) & ~PTE_P);
// 			}
// 
// 			int mtIndex = PTX(i);
// 			if (!(*pte & PTE_PG) ) { 
// 				np->mt[mtIndex].pa = (uint) v2p(mem);
// 			} else{
// 				np->mt[mtIndex].pa = (uint) v2p(mem);
// 				kfree(mem);
// 			}
// 			
// 			if (np->parent->pid <= 2){
// 				np->mt[mtIndex].inMemory = 1;
// 				np->mt[mtIndex].refString = 0;
// 				np->mt[mtIndex].order = order;
// 				order++;
// 				np->mt[mtIndex].offset = -1;
// 			}
// 			else {
// 				np->mt[mtIndex].refString = proc->mt[mtIndex].refString;
// 				np->mt[mtIndex].order = proc->mt[mtIndex].order;
// 				np->mt[mtIndex].offset = proc->mt[mtIndex].offset;
// 				np->mt[mtIndex].inMemory = proc->mt[mtIndex].inMemory;
// 			}
// 		#endif
// 	}
// 	
// 	#ifndef NONE //TODO remove this print
// 	for(i = 0; i < 30; i++){
// 		pte_t * pte;
// 		pte = walkpgdir(proc->pgdir,(void*)(i << PGSHIFT),0);
// 		cprintf("pte %d | value %x | present %d | pageOut %d\n",i ,PTE_ADDR(*pte),*pte & PTE_P,*pte & PTE_PG);
// 	}
// 	cprintf("for new pgdir\n");
// 	for(i = 0; i < 30; i++){
// 		pte_t * pte;
// 		pte = walkpgdir(d,(void*)(i << PGSHIFT),0);
// 		cprintf("pte %d | value %x | present %d | pageOut %d\n",i ,PTE_ADDR(*pte),*pte & PTE_P,*pte & PTE_PG);
// 	}
// 	#endif
// 	cprintf("\n\nexit copyuvm\n");
// 	return d;
// 
// 	bad:
// 	freevm(d);
// 	return 0;
// }

//PAGEBREAK!
// Map user virtual address to kernel address.
char*
uva2ka(pde_t *pgdir, char *uva)
{
  pte_t *pte;

  pte = walkpgdir(pgdir, uva, 0);
  if((*pte & PTE_P) == 0)
    return 0;
  if((*pte & PTE_U) == 0)
    return 0;
  return (char*)p2v(PTE_ADDR(*pte));
}

// Copy len bytes from p to user address va in page table pgdir.
// Most useful when pgdir is not the current page table.
// uva2ka ensures this only works for PTE_U pages.
int
copyout(pde_t *pgdir, uint va, void *p, uint len)
{
  char *buf, *pa0;
  uint n, va0;

  buf = (char*)p;
  while(len > 0){
    va0 = (uint)PGROUNDDOWN(va);
    pa0 = uva2ka(pgdir, (char*)va0);
    if(pa0 == 0)
      return -1;
    n = PGSIZE - (va - va0);
    if(n > len)
      n = len;
    memmove(pa0 + (va - va0), buf, n);
    len -= n;
    buf += n;
    va = va0 + PGSIZE;
  }
  return 0;
}

uint
findNextFreeOffset(){
	int i;
	for(i = 0; i < NUM_PAGES_IN_FILE; i++){
		if (proc->mtOffset[i] == -1){
			proc->mtOffset[i] = i;
			break;
		}
	}
	return i;
}

// This function gets the page address and write the whole page
// to the next free space in pageFileIndexer
// It should also remove the relavent page from physical memory
// int
// storeInPageFile(const void * va){
//     int index;
//     
//     cprintf("enter store with va: %x\n",va);
// 	
// 	pte_t * pte;
// 	if ( (pte = walkpgdir(proc->pgdir,va,0)) == 0){
// 		return -1;
// 	}
// 	
//     //for (index = 0; index < TOTAL_PSYC_PAGES; index++){
//         if ( proc->mt[index].inMemory && (*pte & PTE_P) && proc->mt[index].pa ==  PTE_ADDR(*pte)){ 
// 			
// 			proc->mt[index].offset = findNextFreeOffset();
// 			cprintf("store function: choose offset: %d\n",proc->mt[index].offset);
// 			proc->pgFile->off = (uint) (proc->mt[index].offset * PGSIZE);
// 			
// 			cprintf("try to write to file with offset %d\n",proc->pgFile->off);
// 			if (filewrite(proc->pgFile, (char *)&proc->mt[index].pa, PGSIZE) != PGSIZE)
// 				panic("storeInPageFile: writing to file");
// 			
// 			kfree((char*)p2v(proc->mt[index].pa));
// 			
// 			proc->mt[index].inMemory = 0;
// 			
// 			pte_t * pde = &proc->pgdir[PDX(va)];
// 			uint addrPage = PTE_ADDR(*pde);
// 			pte_t* pgtab = (pte_t*) p2v (addrPage);
// 			pgtab[PTX(va)] = (pgtab[PTX(va)] & ~PTE_P) | PTE_PG;
// 			
// 			proc->nTotalPagedOut++;
// 			cprintf("end store page in file...\n\n");
// 			//break;
// 		}
// 		else{
// 			cprintf("store faild with va: %x\n",va);
// 			return -1;
// 		}
//     //}		
//     return 0;
// }
int
storeInPageFile(void * va){
	int index = PTX(va);
	
	cprintf("enter store with va: %x | index: %d\n",va,index);
// 	int i;
// 	for(i = 0; i < 30; i++){
// 		pte_t * pte;
// 		pte = walkpgdir(proc->pgdir,(void*)(i << PGSHIFT),0);
// 		cprintf("pte %d | value %x | present %d | pageOut %d\n",i ,PTE_ADDR(*pte),*pte & PTE_P,*pte & PTE_PG);
// 	}
	pte_t * pte;
	if ( (pte = walkpgdir(proc->pgdir,va,0)) == 0){
		return -1;
	}
	
		if ( proc->mt[index].inMemory && (*pte & PTE_P) && proc->mt[index].pa ==  PTE_ADDR(*pte)){ 
			
			proc->mt[index].offset = findNextFreeOffset();
			cprintf("store function: choose offset: %d\n",proc->mt[index].offset);
			proc->pgFile->off = (uint) (proc->mt[index].offset * PGSIZE);
			
			cprintf("try to write to file with offset %d\n",proc->pgFile->off);
// 			if (filewrite(proc->pgFile, (char *)&proc->mt[index].pa, PGSIZE) != PGSIZE)
// 				panic("storeInPageFile: writing to file");
// 			
			if (filewrite(proc->pgFile, (char *)p2v(proc->mt[index].pa), PGSIZE) != PGSIZE)
				panic("storeInPageFile: writing to file");
			
			kfree((char*)p2v(proc->mt[index].pa));
			
			proc->mt[index].inMemory = 0;
			proc->mt[index].refString = 0;
			
			pte_t * pde = &proc->pgdir[PDX(va)];
			uint addrPage = PTE_ADDR(*pde);
			pte_t* pgtab = (pte_t*) p2v (addrPage);
			pgtab[PTX(va)] = (pgtab[PTX(va)] & ~PTE_P) | PTE_PG;
			
			proc->nTotalPagedOut++;
			cprintf("end store page in file...\n\n");

		}
		else{
			cprintf("store faild with va: %x\n",va);
			return -1;
		}
	
		return 0;
}

int
loadFromPageFile(void * va){
	int index = PTX(va);
	char* mem;
// 	int i;
// 	for(i = 0; i < 30; i++){
// 		pte_t * pte;
// 		pte = walkpgdir(proc->pgdir,(void*)(i << PGSHIFT),0);
// 		cprintf("pte %d | value %x | present %d | pageOut %d\n",i ,PTE_ADDR(*pte),*pte & PTE_P,*pte & PTE_PG);
// 	}
	cprintf("enter loadFromPageFile with va: %x | entry: %d\n\n",va,PTX(va));

	pte_t * pte;
	if ((pte = walkpgdir(proc->pgdir,va,0)) == 0)
		return -1;

	//cprintf("load: enter kalloc\n");
	if((mem = kalloc()) == 0)
		panic("loadFromPageFile: cannot kalloc");
	memset((void*)mem, 0, PGSIZE);
	//cprintf("load: leave kalloc with %x\n",v2p(mem));
	
	if (!proc->mt[index].inMemory && (proc->mt[index].pa == PTE_ADDR(*pte)) && (*pte & PTE_PG)) {
		proc->pgFile->off = (uint) (proc->mt[index].offset * PGSIZE);

		cprintf("loadFromPageFile : choose offset offset %d\n",proc->mt[index].offset);
		cprintf("loadFromPageFile : try to read from offset %d\n",proc->pgFile->off);
		fileread(proc->pgFile,(char *) mem, PGSIZE);
		
/*		
		for(i = 0; i < 30; i++){
			pte_t * pte;
			pte = walkpgdir(proc->pgdir,(void*)(i << PGSHIFT),0);
			cprintf("pte %d | value %x | present %d | pageOut %d\n",i ,PTE_ADDR(*pte),*pte & PTE_P,*pte & PTE_PG);
		}*/
		cprintf("load: enter mappages\n");

		if(mappages(proc->pgdir,(void*)PGROUNDDOWN((uint)va), PGSIZE, v2p(mem), (PTE_W | PTE_U) & ~PTE_PG & ~PTE_A) < 0)
			panic("loadFromPageFile: mappages");


		cprintf("load: leave mappages\n");
		proc->mt[index].inMemory = 1;
		proc->mtOffset[proc->mt[index].offset] = -1;
		proc->mt[index].pa = v2p(mem);
		proc->mt[index].offset = -1;
				
		#ifdef NFU
			proc->mt[index].refString = 0x80000000;
		#elif FIFO
			proc->mt[index].order = order;
			order++;
		#else
			proc->mt[index].refString = 0;
			proc->mt[index].order = 0;
		#endif
			
// 		pte_t * pde = &proc->pgdir[PDX(va)];
// 		uint addrPage = PTE_ADDR(*pde);
// 		pte_t* pgtab = (pte_t*) p2v (addrPage);
// 		pgtab[PTX(va)] = (pgtab[PTX(va)] | PTE_P) & PTE_PG;
		
	} else{
		cprintf("load faild with va: %x\n",va);
		return -1;
	}
// 	for(i = 0; i < 30; i++){
// 		pte_t * pte;
// 		pte = walkpgdir(proc->pgdir,(void*)(i << PGSHIFT),0);
// 		cprintf("pte %d | value %x | present %d | pageOut %d\n",i ,PTE_ADDR(*pte),*pte & PTE_P,*pte & PTE_PG);
// 	}
	cprintf("exit loadFromPageFile with currentNumberOfPsycPages: %d\n",calculateNumberOfPsycPages());
	return 0;
}
uint
fifo(){
    cprintf("\nenter fifo\n");
    
    int index = 0;
	int i;
    int minOrder,minIndex=0;
    uint res;

	for(index = 0; index < TOTAL_PSYC_PAGES; index++){
		if((proc->mt[index].inMemory ==1) && (proc->mt[index].order != -1)){
			minOrder = proc->mt[index].order;
			break;
		}
	}
	cprintf("fifo: minOrder: %d | index: %d\n",minOrder,index);
    minIndex = index;
    // find the minimum 
    for (i = index; i < TOTAL_PSYC_PAGES; i++){
		//cprintf("fifo: index %d order is %d\n",i,proc->mt[i].order);
		if (proc->mt[i].inMemory && (proc->mt[i].order != -1) && proc->mt[i].order < minOrder){
			minOrder = proc->mt[i].order; 
			minIndex = i;        
        }
    }
    
    cprintf("fifo: index %d | order %d | pa %x\n",minIndex,proc->mt[minIndex].order,proc->mt[minIndex].pa);
    //res = proc->mt[minIndex].pa;
	proc->mt[minIndex].order = -1;
	res = (uint) (minIndex << PGSHIFT);

    return res;
}

uint
nfu(){
	int i, index, minNFU = 0, minIndex = 0;
	uint res = 0;
	
	for(index = 0; index < TOTAL_PSYC_PAGES; index++){
		if (proc->mt[index].inMemory == 1){
			minNFU = proc->mt[index].refString;
			break;
		}
	}
	
	cprintf("nfu: minNFU: %d | index: %d\n",minNFU,index);
    minIndex = index; 
	
	// find the minimum 
	for (i = index; i < TOTAL_PSYC_PAGES; i++){
		if (proc->mt[i].inMemory && proc->mt[i].refString < minNFU)
			minNFU = proc->mt[i].refString; 
			minIndex = i;
	}
	
	cprintf("nfu: index %d | order %d | pa %x\n",minIndex,proc->mt[minIndex].order,proc->mt[minIndex].pa);
	proc->mt[minIndex].refString = 0;
	res = (uint) (minIndex << PGSHIFT);
	
	return res;
}

uint
findNextPageToSwap(){
  
	uint nextva = 0;
 
	#ifdef FIFO
		nextva = fifo();
	#elif NFU
		nextva = nfu();
	#endif
    
	return nextva;
}

void
handlePageFault(){
	
    uint faultAddr = rcr2();
    uint nextPage;
    cprintf("\nenter page faults with addr: %x\n",faultAddr);

	
// 	int i;
// 	for(i = 0; i < 30; i++){
// 		pte_t * pte;
// 		pte = walkpgdir(proc->pgdir,(void*)i,0);
// 		cprintf("pte %d | value %x\n",i,PTE_ADDR(*pte));
// 	}
    if (walkpgdir(proc->pgdir, (void*)faultAddr, 0) == 0){
	    panic("handlePageFault: page not found");
    }
    else{
		pte_t * pte;
		pte = walkpgdir(proc->pgdir, (void*)faultAddr, 0);
		cprintf("handlePageFault: walk is %x\n",PTE_ADDR(*pte));
		if (calculateNumberOfPsycPages() == 15){
			nextPage = findNextPageToSwap();
			storeInPageFile((void *)nextPage);
			loadFromPageFile((void*)faultAddr);
		}
    }
    proc->nPageFaults++;
}

void
updateAccessToPages(){
	int index;
	pte_t* pte;
	
	cprintf("enter update  %d\n",calculateNumberOfPsycPages());
	if (calculateNumberOfPsycPages() > 0){
		for (index = 0; index < TOTAL_PSYC_PAGES; index++){  
			
			pte = walkpgdir(proc->pgdir, (void*) (index << PGSHIFT), 0);
			if (proc->mt[index].inMemory) {
				if (*pte | PTE_A){
					proc->mt[index].refString = (proc->mt[index].refString >> 1) | 0x80000000;
					*pte = *pte & ~PTE_A;
				}
				else
					proc->mt[index].refString = proc->mt[index].refString >> 1;
			}
		}
	}
}


